package pl.edu.agh.jemo.evolution.population;

import java.util.ArrayList;
import java.util.Comparator;

import org.apache.log4j.Logger;

import pl.edu.agh.jemo.evolution.genotype.Genotype;
import pl.edu.agh.jemo.evolution.objfunc.ObjectiveFunctionSet;
import pl.edu.agh.jemo.evolution.operator.crossover.Crossover;
import pl.edu.agh.jemo.evolution.operator.mutation.Mutation;
import pl.edu.agh.jemo.evolution.specimen.Specimen;
import pl.edu.agh.jemo.gui.LoggerHelper;

/**
 * This class is a collection of specimen. It is used to represent data on which
 * genetic algorithm works. Contetnt of population after computations should be
 * treated as a result of algorithm. Population is introduced to make easier
 * operations on every specime, like performing genetic operators, or
 * calculating phenotype. This implementation might work with every specimen
 * type, that extends Specimen class.
 * 
 * @author Marcin Zbijowski
 * 
 */
public class Population extends ArrayList<Specimen> {

	Logger logger = LoggerHelper.getInstance().getLogger();
	/**
	 * As ArrayList is implementation of Serializable interface, it requires
	 * this field to be set.
	 */
	private static final long serialVersionUID = 2730811425036474997L;
	/**
	 * Reference to problem definition used for phenotype calculation. It is
	 * passed to population in its initialization.
	 */
	protected ObjectiveFunctionSet objectiveFunctionSet;

	/**
	 * Mutates every specimen in population.
	 * 
	 * @param mutation
	 *            Mutation reference to be used for specimen modification.
	 */
	public void mutate(Mutation mutation) {
		for (Specimen specimen : this) {
			mutation.performMutation(specimen);
		}
	}

	/**
	 * Recombinates specimen in population based on crossover passed as
	 * parameter.
	 * 
	 * @param crossover
	 *            Crossover operator to be used for specimen recombination.
	 */
	public void recombinate(Crossover crossover) {
		crossover.performCrossover(this);
	}

	/**
	 * Determines phenotype for all specimen in population, based on
	 * objectiveFunctionSet reference. Note that this method assumes,
	 * calculations are done for new population (or old one, that changed), so
	 * population reference in objective function set is refreshed before
	 * calculating.
	 */
	public void calculatePhenotype() {
		objectiveFunctionSet.sortSet();
		objectiveFunctionSet.setPopulationReference(this);
		for (int i = 0; i < objectiveFunctionSet.size(); i++) {
			for (Specimen spec : this) {
				Double val = objectiveFunctionSet.get(i).calculateValue(spec);
				spec.getPhenotype()[i] = val;
			}
		}
	}

	/**
	 * Initializes population as specified by parameters. Creates new specimen
	 * of given type and with given genotype type. Genotype is based on
	 * objective function set passed as another parameter. This method must
	 * always be executed before using genetic algorithm. Please note also, that
	 * only specimen genotype is generated, phenotype and fitness are left
	 * default.
	 * 
	 * @param populationSize
	 *            demanded number of specimen to be created.
	 * @param fun
	 *            problem definition used to determine specimen bounds. Its
	 *            reference is stored by population and used in calculate
	 *            phenotype method.
	 * @param specimenType
	 *            type of specimen to be created, represented by Class object.
	 *            Only subclasses of Specimen are accepted.
	 * @param genotypeType
	 *            type of genotype to be used by generated specimen, represented
	 *            by Class object. Only implementations of Genotype are
	 *            accepted.
	 * @throws InstantiationException
	 *             if creation of new specimen instance fails, for example
	 *             because of lack of nullary constructor.
	 * @throws IllegalAccessException
	 *             if there is no public access nullary constructor defined for
	 *             specimen.
	 */
	public void init(int populationSize, ObjectiveFunctionSet fun,
			Class<? extends Specimen> specimenType,
			Class<? extends Genotype> genotypeType)
			throws InstantiationException, IllegalAccessException {
		objectiveFunctionSet = fun;
		for (int i = 0; i < populationSize; i++) {
			Specimen newSpecimen = specimenType.newInstance();
			newSpecimen.init(fun, genotypeType);
			this.add(newSpecimen);
		}

		logger.debug(String.format("created %d specimen ", populationSize));

		objectiveFunctionSet.setPopulationReference(this);
	}

/*	public void init(int populationSize, ObjectiveFunctionSet fun,
			Class<? extends Specimen> specimenType,
			Class<? extends Genotype> genotypeType)
			throws InstantiationException, IllegalAccessException {
		objectiveFunctionSet = fun;
		for (int i = 0; i < populationSize/4; i++) {
			Specimen newSpecimen = specimenType.newInstance();
			newSpecimen.init(fun, genotypeType);
			this.add(newSpecimen);
			Specimen another = newSpecimen.clone();
			another.getGenotype()[0].fromDouble(-another.getGenotype()[0].asDouble());
			another.getGenotype()[1].fromDouble(-another.getGenotype()[1].asDouble());
			this.add(another);
			
			another = newSpecimen.clone();
			another.getGenotype()[0].fromDouble(-another.getGenotype()[0].asDouble());
			this.add(another);
			
			another = newSpecimen.clone();
			another.getGenotype()[1].fromDouble(-another.getGenotype()[1].asDouble());
			this.add(another);
		}

		logger.debug(String.format("created %d specimen ", populationSize));

		objectiveFunctionSet.setPopulationReference(this);
	}
*/	/**
	 * Creates new instance of population based on current object. New
	 * population is filled with clones of specimen from population, not with
	 * the same references, so it may be used independently. Stored problem
	 * definitions reference is copied as well.
	 * 
	 * @return Clone of population with all contents copied as well.
	 */
	@Override
	public Population clone() {
		Population result = null;
		try {
			result = this.getClass().getConstructor().newInstance();
			for (Specimen spec : this) {
				result.add(spec.clone());
			}
			result.objectiveFunctionSet = objectiveFunctionSet;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return result;
	}

	/**
	 * Returns String object listing contents of population in readable form as
	 * a summary.
	 * 
	 * @return String listing population contents.
	 */
	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		for (Specimen spec : this) {
			result.append(spec.toString());
			result.append("\n");
		}
		result.append("=============================\n");
		return result.toString();
	}

	/**
	 * Returns domain of population represented by two dimensional array. First
	 * dimension of an array is a number of problem dimension. On second array's
	 * dimension there are two values - lower boundary (index 0) and upper
	 * boundary (index 1).
	 * 
	 * @return Two dimensional array describing population domain.
	 */
	public Double[][] getBoundaries() {
		return objectiveFunctionSet.getBoundaries();
	}

	/**
	 * Returns number of dimensions of specimen inside population. Return value
	 * is based on problem definition reference.
	 * 
	 * @return Number of population dimensions.
	 */
	public int getDimensions() {
		return objectiveFunctionSet.getDimensions();
	}

	/**
	 * Allows sorting of population based on specimen genotype. Specimen are
	 * compared against first dimension of genotype, and then, if they equals,
	 * another dimensions are checked. As a result population is sorted mainly
	 * based on first genotype dimension. This comparator should be only used,
	 * to introduced any type of order into population. It is also recommended
	 * in cases where phenotype or fitness might still be undefined.
	 * 
	 * @return Specimen comparator instance, comparing specimen using their
	 *         genotypes.
	 */
	public static Comparator<Specimen> getGenotypeComparator() {
		return new Comparator<Specimen>() {
			@Override
			public int compare(Specimen o1, Specimen o2) {
				for (int i = 0; i < o1.getGenotype().length; i++) {
					if (o1.getGenotype()[i].asDouble() > o2.getGenotype()[i]
							.asDouble()) {
						return -1;
					} else if (o1.getGenotype()[i].asDouble() < o2
							.getGenotype()[i].asDouble()) {
						return 1;
					}
				}
				return 0;
			}
		};
	}

	/**
	 * Allows sorting specimen, based on their fitness. This way of sorting
	 * assures lower index, for the strongest specimen. This comparator should
	 * mostly be used by tournament implementations. Note that this comparator
	 * requires fitness for all specimen, to be calculated.
	 * 
	 * @return Specimen comparator instance, comparing specimen using their
	 *         fitness.
	 */
	// na poczatku bedzie najlepszy (najmniejszy fitness)
	public static Comparator<Specimen> getFitnessComparator() {
		return new Comparator<Specimen>() {
			@Override
			public int compare(Specimen o1, Specimen o2) {
				if (o1.getFitness() > o2.getFitness()) {
					return 1;
				} else if (o1.getFitness() < o2.getFitness()) {
					return -1;
				}
				return 0;
			}
		};
	}

	// na poczatku bedzie najlepszy (najmocniejszy fenotyp)
	public static Comparator<Specimen> getSpecimenPhenotype0Comparator() {
		return new Comparator<Specimen>() {
			@Override
			public int compare(Specimen o1, Specimen o2) {
				if (o1.getPhenotype()[0] == o2.getPhenotype()[0])
					return 0;
				else if (o1.getPhenotype()[0] > o2.getPhenotype()[0])
					return -1;
				else
					return 1;
			}
		};
	}

	// na poczatku najmniejszy genotyp
	public static Comparator<Specimen> getSpecimenDimensionGenotypeComparator(
			final int dimension) {
		return new Comparator<Specimen>() {
			@Override
			public int compare(Specimen o1, Specimen o2) {
				if (o1.getGenotype()[dimension].asDouble() < o2.getGenotype()[dimension]
						.asDouble())
					return -1;
				if (o1.getGenotype()[dimension].asDouble() > o2.getGenotype()[dimension]
						.asDouble())
					return 1;
				return 0;
			}
		};

	}

	public ObjectiveFunctionSet getObjectiveFunctionSet() {
		return objectiveFunctionSet;
	}

	public void setObjectiveFunctionSet(ObjectiveFunctionSet functionSet) {
		this.objectiveFunctionSet = functionSet;
	}
}
